//      TITLE("Interval and Profile Clock Interrupts")
//++
//
// Copyright (c) 1990  Microsoft Corporation
//
// Module Name:
//
//    xxclock.s
//
// Abstract:
//
//    This module implements the code necessary to field and process the
//    interval and profile clock interrupts.
//
// Author:
//
//    David N. Cutler (davec) 27-Mar-1990
//
// Environment:
//
//    Kernel mode only.
//
// Revision History:
//
//--

#include "ksmips.h"

//
// Define external variables that can be addressed using GP.
//

        .extern KeMaximumIncrement      4
        .extern KeTickCount             3 * 4
        .extern KeTimeAdjustment        4
        .extern KiAdjustDpcThreshold    4
        .extern KiIdealDpcRate          4
        .extern KiMaximumDpcQueueDepth  4
        .extern KiProfileListHead       2 * 4
        .extern KiProfileLock           4
        .extern KiTickOffset            4

        SBTTL("Update System Time")
//++
//
// VOID
// KeUpdateSystemTime (
//    IN PKTRAP_FRAME TrapFrame,
//    IN ULONG TimeIncrement
//    )
//
// Routine Description:
//
//    This routine is entered as the result of an interrupt generated by the
//    interval timer. Its function is to update the system time and check to
//    determine if a timer has expired.
//
//    N.B. This routine is executed on a single processor in a multiprocess
//       system. The remainder of the processors only execute the quantum end
//       and runtime update code.
//
// Arguments:
//
//    TrapFrame (a0) - Supplies a pointer to a trap frame.
//
//    TimeIncrement (a1) - Supplies the time increment in 100ns units.
//
// Return Value:
//
//    None.
//
//--

        LEAF_ENTRY(KeUpdateSystemTime)

//
// Update the interrupt time.
//

        ld      t8,KiPcr2 + Pc2InterruptTime // get interrupt time
        daddu   t8,t8,a1                // add time increment value
        sd      t8,KiPcr2 + Pc2InterruptTime // store interrupt time

//
// Update tick offset and check for "system clock" tick.
//

        lw      a2,KiTickOffset         // get tick offset value
        sub     a2,a2,a1                // subtract time increment
        ld      v0,KeTickCount          // get low and high 1 tick count
        la      t0,KiTimerTableListHead // get base address of timer table
        sw      a2,KiTickOffset         // store tick offset value
        bgtz    a2,10f                  // if gtz, tick not completed
        lw      a3,KeMaximumIncrement   // get maximum increment value

//
// Update system time.
//

        lw      t1,KeTimeAdjustment     // get time adjustment value
        ld      t2,KiPcr2 + Pc2SystemTime // get low and high 1 system time
        daddu   t2,t2,t1                // add time increment value
        sd      t2,KiPcr2 + Pc2SystemTime // store low nad high 1 system time

//
// Update the tick count.
//
// N.B. The tick count is updated in a very strict manner so that an
//      interlock does not have to be used in an MP system. This is
//      required for backward compatibility with old drivers and file
//      systems.
//

        daddu   t2,v0,1                 // increment tick count
        dsrl    t3,t2,32                // get high half of tick count
        sw      t2,KiPcr2 + Pc2TickCountLow(zero) // store low tick count

        .set    noreorder
        .set    noat
        sw      t3,KeTickCount + 8      // store high 2 tick count
        sd      t2,KeTickCount          // store low and high 1 tick count
        .set    at
        .set    reorder

//
// Compute next tick offset value.
//

        addu    a3,a3,a2                // add maximum increment to residue
        sw      a3,KiTickOffset         // store tick offset value

//
// Check to determine if a timer has expired at the current hand value.
//

        and     t1,v0,TIMER_TABLE_SIZE - 1  // reduce to table table index
        sll     t2,t1,3                 // compute timer table listhead address
        addu    t2,t2,t0                //
        lw      t3,LsFlink(t2)          // get address of first timer in list
        beq     t2,t3,5f                // if eq, no timer active

//
// Get the expiration time from the timer object.
//
// N.B. The offset to the timer list entry must be subtracted out of the
//      displacement calculation.
//

        ld      t4,TiDueTime - TiTimerListEntry(t3) // get timer due time
        sltu    t9,t8,t4                // check if timer is due
        beq     zero,t9,20f             // if eq, timer has expired

//
// Check to determine if a timer has expired at the next hand value.
//

5:      addu    v0,v0,1                 // advance hand value to next entry
10:     and     t1,v0,TIMER_TABLE_SIZE - 1  // reduce to table table index
        sll     t2,t1,3                 // compute timer table listhead address
        addu    t2,t2,t0                //
        lw      t3,LsFlink(t2)          // get address of first timer in list
        beq     t2,t3,40f               // if eq, no timer active

//
// Get the expiration time from the timer object.
//
// N.B. The offset to the timer list entry must be subtracted out of the
//      displacement calculation.
//

        ld      t4,TiDueTime - TiTimerListEntry(t3) // get timer due time
        sltu    t9,t8,t4                // check if timer is due
        bne     zero,t9,40f             // if ne, timer has not expired

//
// Put timer expiration DPC in the system DPC list and initiate a dispatch
// interrupt on the current processor.
//

20:     la      t0,KiTimerExpireDpc     // get expiration DPC address
        lw      a1,KiPcr + PcPrcb(zero) // get address of PRCB

        DISABLE_INTERRUPTS(t2)          // disable interrupts

        addu    t3,a1,PbDpcListHead     // compute DPC listhead address
        addu    v1,a1,PbDpcLock         // compute DPC lock address

#if !defined(NT_UP)

30:     ll      t4,0(v1)                // get current lock value
        move    t5,t3                   // set lock ownership value
        bne     zero,t4,30b             // if ne, spin lock owned
        sc      t5,0(v1)                // set spin lock owned
        beq     zero,t5,30b             // if eq, store conditional failed

#endif

        lw      t4,DpLock(t0)           // get DPC inserted state
        bne     zero,t4,35f             // if ne, DPC entry already inserted
        lw      t4,LsBlink(t3)          // get address of last entry in list
        sw      v1,DpLock(t0)           // set DPC inserted state
        sw      v0,DpSystemArgument1(t0) // set timer table hand value
        addu    t0,t0,DpDpcListEntry    // compute address of DPC list entry
        sw      t0,LsBlink(t3)          // set address of new last entry
        sw      t0,LsFlink(t4)          // set next link in old last entry
        sw      t3,LsFlink(t0)          // set address of next entry
        sw      t4,LsBlink(t0)          // set address of previous entry
        lw      t5,PbDpcQueueDepth(a1)  // increment DPC queue depth
        addu    t5,t5,1                 //
        sw      t5,PbDpcQueueDepth(a1)  //

        .set    noreorder
        .set    noat
        mfc0    t3,cause                // get exception cause register
        or      t3,t3,DISPATCH_INTERRUPT // merge dispatch interrut request
        mtc0    t3,cause                // set exception cause register
        .set    at
        .set    reorder

35:                                     //

#if !defined(NT_UP)

        sw      zero,0(v1)              // set spin lock not owned

#endif

        ENABLE_INTERRUPTS(t2)           // enable interrupts

40:     blez    a2,50f                  // if lez, full tick
        j       ra                      // return
50:     j       KeUpdateRunTime

        .end    KeUpdateSystemTime

        SBTTL("Update Thread and Process Runtime")
//++
//
// VOID
// KeUpdateRunTime (
//    IN PKTRAP_FRAME TrapFrame
//    )
//
// Routine Description:
//
//    This routine is entered as the result of an interrupt generated by the
//    interval timer. Its function is to update the runtime of the current
//    thread, update the runtime of the current thread's process, and decrement
//    the current thread's quantum.
//
//    N.B. This routine is executed on all processors in a multiprocess system.
//
// Arguments:
//
//    TrapFrame (a0) - Supplies a pointer to a trap frame.
//
// Return Value:
//
//    None.
//
//--

        LEAF_ENTRY(KeUpdateRunTime)

        lw      t0,KiPcr + PcCurrentThread(zero) // get current thread address
        lw      t2,ThApcState + AsProcess(t0) // get address of current process
        lw      t3,TrPsr(a0)            // get saved processor status
        lw      t5,KiPcr + PcPrcb(zero) // get current processor block address
        lw      t7,PbDpcRoutineActive(t5) // get DPC active flag
        and     t4,t3,0x1 << PSR_PMODE  // isolate previous processor mode
        bne     zero,t4,30f             // if ne, previous mode was user

//
// If a DPC is active, then increment the time spent executing DPC routines.
// Otherwise, if the old IRQL is greater than DPC level, then increment the
// time spent executing interrupt services routines. Otherwise, increment
// the time spent in kernel mode for the current thread.
//

        lbu     t6,TrOldIrql(a0)        // get previous IRQL
        subu    t6,t6,DISPATCH_LEVEL    // compare IRQL with DPC level
        bltz    t6,20f                  // if ltz, increment thread kernel time
        addu    t8,t5,PbInterruptTime   // compute interrupt time address
        bgtz    t6,10f                  // if gtz, increment interrupt time
        addu    t8,t5,PbDpcTime         // compute DPC time address
        beq     zero,t7,20f             // if eq, increment thread kernel time

//
// Update the time spend in DPC/interrupt processing.
//

10:     lw      t6,0(t8)                // get processor time
        addu    t6,t6,1                 // increment processor time
        sw      t6,0(t8)                // store processor time
        addu    t9,t5,PbKernelTime      // compute processor kernel time address
        b       50f                     //

//
// Update the time spent in kernel mode for the current thread.
//

20:     lw      t6,ThKernelTime(t0)     // get kernel time
        addu    t6,t6,1                 // increment kernel time
        sw      t6,ThKernelTime(t0)     // store kernel time
        addu    t2,t2,PrKernelTime      // compute process kernel time address
        addu    t9,t5,PbKernelTime      // compute processor kernel time address
        b       40f                     //

//
// Update the time spent in user mode for the current thread.
//

30:     lw      t6,ThUserTime(t0)       // get user time
        addu    t6,t6,1                 // increment user time
        sw      t6,ThUserTime(t0)       // store user time
        addu    t2,t2,PrUserTime        // compute process user time address
        addu    t9,t5,PbUserTime        // compute processor user time address

//
// Update the time spent in kernel/user mode for the current thread's process.
//
// N.B. The update of the process time must be synchronized across processors.
//

40:     ll      t6,0(t2)                // get process time
        addu    t6,t6,1                 // increment process time
        sc      t6,0(t2)                // store process time
        beq     zero,t6,40b             // if eq, store conditional failed

//
// Update the time spent in kernel/user mode for the current processor.
//

50:     lw      t6,0(t9)                // get processor time
        addu    t6,t6,1                 // increment processor time
        sw      t6,0(t9)                // store processor time

//
// Update the DPC request rate which is computed as the average between
// the previous rate and the current rate.
//

        lw      a0,PbDpcCount(t5)       // get current DPC count
        lw      a1,PbDpcLastCount(t5)   // get last DPC count
        lw      a2,PbDpcRequestRate(t5) // get last DPC request rate
        lw      a3,PbDpcQueueDepth(t5)  // get current DPC queue depth
        sw      a0,PbDpcLastCount(t5)   // set last DPC count
        subu    a0,a0,a1                // compute count during interval
        addu    a0,a0,a2                // compute sum of current and last
        srl     a0,a0,1                 // average current and last
        sw      a0,PbDpcRequestRate(t5) // set new DPC request rate

//
// If the current DPC queue depth is not zero, a DPC routine is not active,
// and a DPC interrupt has not been requested, then request a dispatch
// interrupt, decrement the maximum DPC queue depth, and reset the threshold
// counter if appropriate.
//

        lw      v0,PbDpcInterruptRequested(t5) // get DPC interrupt requested
        beq     zero,a3,60f             // if eq, DPC queue is empty
        or      v0,v0,t7                // merge DPC interrupt requested and active
        bne     zero,v0,60f             // if ne, DPC active or interrupt requested

        DISABLE_INTERRUPTS(a1)          // disable interrupt

        .set    noreorder
        .set    noat
        mfc0    a2,cause                // get exception cause register
        lw      v0,PbMaximumDpcQueueDepth(t5) // get maximum queue depth
        lw      v1,KiIdealDpcRate       // get ideal DPC rate
        or      a2,a2,DISPATCH_INTERRUPT // merge dispatch interrut request
        mtc0    a2,cause                // set exception cause register
        .set    at
        .set    reorder

        ENABLE_INTERRUPTS(a1)           // enable interrupts

        sltu    a0,a0,v1                // test if current rate less than ideal
        lw      a1,KiAdjustDpcThreshold // reset initial threshold counter
        sw      a1,PbAdjustDpcThreshold(t5) //
        beq     zero,a0,KiDecrementQuantum // if eq, rate greater or equal ideal
        subu    v0,v0,1                 // decrement maximum DPC queue depth
        beq     zero,v0,KiDecrementQuantum // if eq, current value is one
        sw      v0,PbMaximumDpcQueueDepth(t5) // set new maximum DPC queue depth
        b       KiDecrementQuantum      //

//
// The DPC queue is empty or a DPC routine is active or a DPC interrupt
// has been requested. Count down the adjustment threshold and if the
// count reaches zero, then increment the maximum DPC queue depth, but
// no above the initial value and reset the adjustment threshold value.
//

60:     lw      a0,PbAdjustDpcThreshold(t5) // get adjustment threshold counter
        lw      a1,PbMaximumDpcQueueDepth(t5) // get current maximum queue depth
        lw      a2,KiMaximumDpcQueueDepth // get initial maximum queue depth
        subu    a0,a0,1                 // decrement adjustment threshold counter
        sw      a0,PbAdjustDpcThreshold(t5) //
        bne     zero,a0,KiDecrementQuantum // if ne, adjustment counter not zero
        lw      a0,KiAdjustDpcThreshold //set new DPC threshold counter
        sw      a0,PbAdjustDpcThreshold(t5) //
        beq     a1,a2,KiDecrementQuantum // if eq, currently at maximum depth
        addu    a1,a1,1                 // increment current maximum queue depth
        sw      a1,PbMaximumDpcQueueDepth(t5) // set new maximum DPC queue depth

//
// Decrement current thread quantum and check to determine if a quantum end
// has occurred.
//

        ALTERNATE_ENTRY(KiDecrementQuantum)

        lb      t6,ThQuantum(t0)        // get current thread quantum
        sub     t6,t6,CLOCK_QUANTUM_DECREMENT // decrement current quantum
        sb      t6,ThQuantum(t0)        // store thread quantum
        bgtz    t6,60f                  // if gtz, quantum remaining

//
// Set quantum end flag and initiate a dispatch interrupt on the current
// processor.
//

        lw      t1,PbIdleThread(t5)     // get address of idle
        beq     t0,t1,60f               // if eq, idle thread
        sw      sp,KiPcr + PcQuantumEnd(zero) // set quantum end indicator

        DISABLE_INTERRUPTS(t0)          // disable interrupts

        .set    noreorder
        .set    noat
        mfc0    t1,cause                // get exception cause register
        or      t1,t1,DISPATCH_INTERRUPT // merge dispatch interrupt request
        mtc0    t1,cause                // set exception cause register
        nop                             // 1 cycle hazzard
        .set    at
        .set    reorder

        ENABLE_INTERRUPTS(t0)           // enable interrupts

60:     j       ra                      // return

        .end    KeUpdateRunTime


        SBTTL("Process Profile Interrupt")
//++
//
// VOID
// KeProfileInterruptWithSource (
//    IN PKTRAP_FRAME TrapFrame,
//    IN KPROFILE_SOURCE ProfileSource
//    )
//
// VOID
// KeProfileInterrupt (
//    IN PKTRAP_FRAME TrapFrame
//    )
//
// Routine Description:
//
//    This routine is entered as the result of an interrupt generated by the
//    profile timer. Its function is to update the profile information for
//    the currently active profile objects.
//
//    N.B. This routine is executed on all processors in a multiprocess system.
//
// Arguments:
//
//    TrapFrame (a0) - Supplies a pointer to a trap frame.
//
//    ProfileSource (a1) - Supplies the source of the profile interrupt
//              KeProfileInterrupt is an alternate entry for backwards
//              compatibility that sets the source to zero (ProfileTime)
//
// Return Value:
//
//    None.
//
//--

        .struct 0
        .space  4 * 4                   // argument save area
        .space  3 * 4                   //
PfRa:   .space  4                       // return address
ProfileFrameLength:                     // profile frame length

        NESTED_ENTRY(KeProfileInterrupt, ProfileFrameLength, zero)

        move    a1, zero                // set profile source to ProfileTime

        ALTERNATE_ENTRY(KeProfileInterruptWithSource)

        subu    sp,sp,ProfileFrameLength // allocate stack frame
        sw      ra,PfRa(sp)             // save return address

        PROLOGUE_END

#if !defined(NT_UP)

10:     ll      t0,KiProfileLock        // get current lock value
        move    t1,s0                   // set ownership value
        bne     zero,t0,10b             // if ne, spin lock owned
        sc      t1,KiProfileLock        // set spin lock owned
        beq     zero,t1,10b             // if eq, store conditional failed

#endif

        lw      a2,KiPcr + PcCurrentThread(zero) // get current thread address
        lw      a2,ThApcState + AsProcess(a2) // get address of current process
        addu    a2,a2,PrProfileListHead  // compute profile listhead address
        jal     KiProcessProfileList     // process the process profile list
        la      a2,KiProfileListHead     // get profile listhead address
        jal     KiProcessProfileList     // process the system profile list

#if !defined(NT_UP)

        sw      zero,KiProfileLock      // set spin lock not owned

#endif

        lw      ra,PfRa(sp)             // restore return address
        addu    sp,sp,ProfileFrameLength // deallocate stack frame
        j       ra                      // return

        .end    KeProfileInterrupt

        SBTTL("Process Profile List")
//++
//
// VOID
// KiProcessProfileList (
//    IN PKTRAP_FRAME TrapFrame,
//    IN KPROFILE_SOURCE Source,
//    IN PLIST_ENTRY ListHead
//    )
//
// Routine Description:
//
//    This routine is called to process a profile list.
//
// Arguments:
//
//    TrapFrame (a0) - Supplies a pointer to a trap frame.
//
//    Source (a1) - Supplies profile source to match
//
//    ListHead (a2) - Supplies a pointer to a profile list.
//
// Return Value:
//
//    None.
//
//--

        LEAF_ENTRY(KiProcessProfileList)

        lw      t8,LsFlink(a2)          // get address of next entry
        li      a3,0xfffffffc           // set bucket mask value
        beq     a2,t8,30f               // if eq, end of list
        lw      t0,TrFir(a0)            // get interrupt PC address
        lw      t6,KiPcr + PcSetMember(zero) // get current processor member

//
// Scan profile list and increment profile buckets as appropriate.
//

10:     lw      t1,PfRangeBase - PfProfileListEntry(t8) // get base of range
        lw      t2,PfRangeLimit - PfProfileListEntry(t8) // get limit of range
        lhu     t3,PfSource - PfProfileListEntry(t8) // get source
        lhu     t4,PfAffinity - PfProfileListEntry(t8) // get affinity
        bne     t3,a1,20f               // if ne, source mismatch
        sltu    v0,t0,t1                // check against range base
        sltu    v1,t0,t2                // check against range limit
        and     t5,t6,t4                // check against processor
        bne     zero,v0,20f             // if ne, less that range base
        beq     zero,v1,20f             // if eq, not less that range limit
        beq     zero,t5,20f             // if eq, affinity mismatch
        subu    t1,t0,t1                // compute offset in range
        lw      t2,PfBucketShift - PfProfileListEntry(t8) // get shift count
        lw      v0,PfBuffer - PfProfileListEntry(t8) // get profile buffer address
        srl     v1,t1,t2                // compute bucket offset
        and     v1,v1,a3                // clear low order offset bits
        addu    v1,v1,v0                // compute bucket address
        lw      v0,0(v1)                // increment profile bucket
        addu    v0,v0,1                 //
        sw      v0,0(v1)                //
20:     lw      t8,LsFlink(t8)          // get address of next entry
        bne     a2,t8,10b               // if ne, more entries in profile list
30:     j       ra                      // return

        .end    KiProcessProfileList
